//Headers
#include <cstdio>
#include <fstream>
#include <string>
#include <sstream>
#include <vector>
#include <SDL/SDL.h>
#include <SDL/SDL_image.h>
//#include <SDL/SDL_rotozoom.h>
//#include <SDL/SDL_gfxPrimitives.h>
#include <SDL/SDL_opengl.h>
#include <GL/gl.h>
#include <GL/glext.h>
#include <GL/glu.h>
#include <Box2D/Box2D.h>
#include "coms/timer.h"
#include "coms/funcs.h"
#include "coms/sdlfuncs.h"
#include "coms/stb_image.h"
#include "coms/entities.h"
//#include "coms/sdlObj.h"
using namespace std;

//Global Game variables
//Pi on 4
float pio4 = 0.7853981634;
//Screen Attributes
const int SCREEN_WIDTH = 640;
const int SCREEN_HEIGHT = 480;
const int SCREEN_BPP = 32;
const char* SCREEN_CAPTION = "Stacker";
//Quit varidable
bool quit = false;
//Screen that is drawn box (camera)
SDL_Rect c_screen;
//Total world box
SDL_Rect w_screen;
//Frame rate
const int FRAMES_PER_SECOND = 60;

//Attributes of player
int P_X, P_Y = 0;

//Attributes of environ
//Vector of integers
//each value is X,Y,W,H
vector< vector< int > > rawenv;
int N_SOLIDS;

// Define the gravity vector.
b2Vec2 gravity(0.0f, 30.0f);

// Do we want to let bodies sleep?
bool doSleep = true;

// Construct a world object, which will hold and simulate the rigid bodies.
//Set scale value
int P_SCALE = 16;
//World
b2World physWorld(gravity, doSleep);

//The event structure that will be used
SDL_Event event;

//GL
//Env Sprits
const int NumSprites = 9;
const char* fname = "textures/envs/1.tga";
const char* fNames[NumSprites];
GLuint envSprites[NumSprites];

//Collision Callback
//class CollisionCallback();

//Basic stuff
bool GL_init()
{
    glEnable(GL_TEXTURE_2D);
    glEnable(GL_ALPHA_TEST);
    glAlphaFunc(GL_EQUAL, 1);
    //glEnable(GL_BLEND);
    //glBlendFunc(GL_SRC_ALPHA,GL_ONE);
    glClearColor(1.0f,1.0f,1.0f,1.0f);
    glClear(GL_COLOR_BUFFER_BIT);
    glMatrixMode(GL_PROJECTION);
    glLoadIdentity();
    glOrtho(0,SCREEN_WIDTH, SCREEN_HEIGHT, 0, -1, 1);
    glMatrixMode(GL_MODELVIEW);
    glLoadIdentity();
    if(glGetError() != GL_NO_ERROR)
    {
        return false;
    }
    return true;
}

bool init()
{
    //Initialize all SDL subsystems
    if( SDL_Init( SDL_INIT_EVERYTHING ) == -1 )
    {
        return false;
    }

    //Set up screen
    SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1);
    SDL_SetVideoMode( SCREEN_WIDTH, SCREEN_HEIGHT, SCREEN_BPP, SDL_OPENGL );
    //World
    w_screen.x = 0;
    w_screen.y = 0;
    //Camera
    c_screen.w = SCREEN_WIDTH;
    c_screen.h = SCREEN_HEIGHT;
    c_screen.x = 0;
    c_screen.y = 0;

    //Init OPENGL
    if(!GL_init())
    {
        return false;
    }

    //If there was an opengl error setting up the screen
    if(glGetError() != GL_NO_ERROR)
    {
        return false;
    }

    //Set the window caption
    SDL_WM_SetCaption( SCREEN_CAPTION, SCREEN_CAPTION);

    //If everything initialised fine
    return true;
}

void load_map(const char* filename)
{
    ifstream mfile;
    mfile.open(filename);
    string line;
    vector<string> lines;
    while( getline( mfile, line ) )
    {
        lines.push_back( line );
    }
    int wstart, estart;
    vector<int> loca = split2by4(lines[1]);
    wstart = loca[0];
    estart = loca[1];
    vector<int> wrld = split4by4(lines[wstart]);
    w_screen.w = wrld[0];
    w_screen.h = wrld[1];
    P_X = wrld[2];
    P_Y = wrld[3];
    for(int i = estart; i < int(lines.size()); i++)
    {
        rawenv.push_back(split5by4(lines[i]));
    }
    N_SOLIDS = rawenv.size();

    //Gen Gl Tex
    fNames[0] = "textures/envs/0.tga";
    fNames[1] = "textures/envs/1.tga";
    fNames[2] = "textures/envs/2.tga";
    fNames[3] = "textures/envs/3.tga";
    fNames[4] = "textures/envs/4.tga";
    fNames[5] = "textures/envs/5.tga";
    fNames[6] = "textures/envs/6.tga";
    fNames[7] = "textures/envs/7.tga";
    fNames[8] = "textures/envs/8.tga";
    int width, height, n; //These really aren't used, but I have to declare them
    unsigned char* data;
    for(int i = 0; i < NumSprites; i++)
    {
        data = stbi_load(fNames[i], &width, &height, &n, 4);
        // request RGBA
        if(data != NULL)
        {
            // use image in data
            glGenTextures(1, &envSprites[i]);
            glBindTexture(GL_TEXTURE_2D, envSprites[i]);
            glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
            glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
            glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
            glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
            glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, data);
            //Free image data
            stbi_image_free(data);
        }
    }
}

void clean_up()
{
    //Quit SDL
    SDL_Quit();
}

//Main
int main( int argc, char** argv )
{
    //Set up Box2D
    B2_NOT_USED(argc);
	B2_NOT_USED(argv);

	//Variables
	float32 timeStep = 1.0f / 60.0f;
	int32 velocityIterations = 6;
	int32 positionIterations = 2;

    //FPS regulator
    Timer fps;

    //Initialise
    if( init() == false )
    {
        return 1;
    }

    //Load map
    load_map("maps/map0.txt");

    //Set up walls
    //Environ classes array
    EnvRect envA[N_SOLIDS];

    //Solid objects array
    SDL_Rect solids[N_SOLIDS];

    for(int i = 0; i < N_SOLIDS; i++)
    {
        envA[i].specify(rawenv[i][0], rawenv[i][1], rawenv[i][2], rawenv[i][3], rawenv[i][4], envSprites[rawenv[i][4]], &physWorld);
        solids[i] = envA[i].getbox();
    }

    //Player
    Player myPlayer(P_X, P_Y, &physWorld, "textures/player.tga", "textures/gun1.tga");

    //Test Box
    Block tB(64, 64, 0, b2Vec2(0,0), &physWorld);

    //While the user has not quit
    while( quit == false )
    {
        //Start fps timer
        fps.start();

        //While there's an event to handle
        while( SDL_PollEvent( &event ))
        {
            //If the user has Xed out of the window
            if( event.type == SDL_QUIT )
            {
                //Quit the program
                quit = true;
            }

            if(event.type == SDL_KEYDOWN)
            {
                if( event.key.keysym.sym == SDLK_ESCAPE )
                {
                    quit = true;
                }
            }
        }

        // Instruct the world to perform a single step of simulation.
		// It is generally best to keep the time step and iterations fixed.
		physWorld.Step(timeStep, velocityIterations, positionIterations);

		//Collision Listener
		//physWorld.SetContactListener();

        //Handle square input
        myPlayer.handle_input();

        //Move square
        myPlayer.updatepos();

        //Check if player is standing
        myPlayer.checkStanding(solids, N_SOLIDS);

        //Update camera position
        c_screen.x = myPlayer.getbox().x - (c_screen.w / 2);
        c_screen.y = myPlayer.getbox().y - (c_screen.h / 2);

        if(c_screen.x < 0)
        {
            c_screen.x = 0;
        }

        if(c_screen.x > (w_screen.w - c_screen.w))
        {
            c_screen.x = (w_screen.w - c_screen.w);
        }

        if(c_screen.y < 0)
        {
            c_screen.y = 0;
        }
        else
        {
            if(c_screen.y > (w_screen.h - c_screen.h))
            {
                c_screen.y = (w_screen.h - c_screen.h);
            }
        }

        //Update tB
        tB.update();

        //Fill screen white
        glClear(GL_COLOR_BUFFER_BIT);

        //Show walls
        for(int i = 0; i < N_SOLIDS; i++)
        {
            envA[i].show(c_screen);
        }

        //Show player
        myPlayer.show(c_screen);

        //Show test block
        tB.show(c_screen);

        //OpenGl Flip
        SDL_GL_SwapBuffers();

        //Cap framerate
        if( fps.get_ticks() < 1000 / FRAMES_PER_SECOND )
        {
            SDL_Delay( ( 1000 / FRAMES_PER_SECOND ) - fps.get_ticks() );
        }
    }

    //Clean
    clean_up();

    //Return
    return 0;
}
